// Copyright (c) 2024 CHANGLEI. All rights reserved.

import 'dart:math' as math;

import 'package:charts_grasp/src/chart/axis/axis_renderer.dart';
import 'package:charts_grasp/src/chart/axis/category_axis.dart';
import 'package:charts_grasp/src/chart/axis/datetime_axis.dart';
import 'package:charts_grasp/src/chart/axis/datetime_category_axis.dart';
import 'package:charts_grasp/src/chart/axis/logarithmic_axis.dart';
import 'package:charts_grasp/src/chart/axis/multi_level_labels.dart';
import 'package:charts_grasp/src/chart/axis/numeric_axis.dart';
import 'package:charts_grasp/src/chart/axis/plotband.dart';
import 'package:charts_grasp/src/chart/base/chart_base.dart';
import 'package:charts_grasp/src/chart/chart_series/series.dart';
import 'package:charts_grasp/src/chart/chart_series/series_renderer_properties.dart';
import 'package:charts_grasp/src/chart/chart_series/xy_data_series.dart';
import 'package:charts_grasp/src/chart/common/cartesian_state_properties.dart';
import 'package:charts_grasp/src/chart/common/common.dart';
import 'package:charts_grasp/src/chart/common/interactive_tooltip.dart';
import 'package:charts_grasp/src/chart/utils/enum.dart';
import 'package:charts_grasp/src/chart/utils/helper.dart';
import 'package:charts_grasp/src/common/event_args.dart';
import 'package:charts_grasp/src/common/rendering_details.dart';
import 'package:charts_grasp/src/common/utils/enum.dart';
import 'package:charts_grasp/src/common/utils/helper.dart';
import 'package:charts_grasp/src/common/utils/typedef.dart' show ChartLabelFormatterCallback, MultiLevelLabelFormatterCallback;
import 'package:charts_grasp/src/core/core.dart';
import 'package:charts_grasp/src/core/theme.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

abstract class ChartAxis {
  ChartAxis({
    this.name,
    double? plotOffset,
    bool? isVisible,
    bool? anchorRangeToVisiblePoints,
    AxisTitle? title,
    AxisLine? axisLine,
    ChartRangePadding? rangePadding,
    int? labelRotation,
    ChartDataLabelPosition? labelPosition,
    LabelAlignment? labelAlignment,
    TickPosition? tickPosition,
    MajorTickLines? majorTickLines,
    MinorTickLines? minorTickLines,
    this.labelStyle,
    AxisLabelIntersectAction? labelIntersectAction,
    this.desiredIntervals,
    MajorGridLines? majorGridLines,
    MinorGridLines? minorGridLines,
    int? maximumLabels,
    int? minorTicksPerInterval,
    bool? isInversed,
    bool? opposedPosition,
    EdgeLabelPlacement? edgeLabelPlacement,
    bool? enableAutoIntervalOnZooming,
    double? zoomFactor,
    double? zoomPosition,
    InteractiveTooltip? interactiveTooltip,
    this.interval,
    this.crossesAt,
    this.associatedAxisName,
    bool? placeLabelsNearAxisLine,
    List<PlotBand>? plotBands,
    this.rangeController,
    this.maximumLabelWidth,
    this.labelsExtent,
    this.autoScrollingDelta,
    AutoScrollingMode? autoScrollingMode,
    double? borderWidth,
    Color? borderColor,
    AxisBorderType? axisBorderType,
    MultiLevelLabelStyle? multiLevelLabelStyle,
    this.multiLevelLabelFormatter,
    this.multiLevelLabels,
    this.axisLabelFormatter,
  })  : isVisible = isVisible ?? true,
        anchorRangeToVisiblePoints = anchorRangeToVisiblePoints ?? true,
        interactiveTooltip = interactiveTooltip ?? const InteractiveTooltip(),
        isInversed = isInversed ?? false,
        plotOffset = plotOffset ?? 0,
        placeLabelsNearAxisLine = placeLabelsNearAxisLine ?? true,
        opposedPosition = opposedPosition ?? false,
        rangePadding = rangePadding ?? ChartRangePadding.auto,
        labelRotation = labelRotation ?? 0,
        labelPosition = labelPosition ?? ChartDataLabelPosition.outside,
        labelAlignment = labelAlignment ?? LabelAlignment.center,
        tickPosition = tickPosition ?? TickPosition.outside,
        labelIntersectAction = labelIntersectAction ?? AxisLabelIntersectAction.hide,
        minorTicksPerInterval = minorTicksPerInterval ?? 0,
        maximumLabels = maximumLabels ?? 3,
        title = title ?? AxisTitle(),
        axisLine = axisLine ?? const AxisLine(),
        majorTickLines = majorTickLines ?? const MajorTickLines(),
        minorTickLines = minorTickLines ?? const MinorTickLines(),
        majorGridLines = majorGridLines ?? const MajorGridLines(),
        minorGridLines = minorGridLines ?? const MinorGridLines(),
        edgeLabelPlacement = edgeLabelPlacement ?? EdgeLabelPlacement.none,
        zoomFactor = zoomFactor ?? 1,
        zoomPosition = zoomPosition ?? 0,
        enableAutoIntervalOnZooming = enableAutoIntervalOnZooming ?? true,
        plotBands = plotBands ?? <PlotBand>[],
        autoScrollingMode = autoScrollingMode ?? AutoScrollingMode.end,
        axisBorderType = axisBorderType ?? AxisBorderType.rectangle,
        borderColor = borderColor ?? Colors.transparent,
        borderWidth = borderWidth ?? 0.0,
        multiLevelLabelStyle = multiLevelLabelStyle ?? const MultiLevelLabelStyle();

  final bool isVisible;

  final bool anchorRangeToVisiblePoints;

  final AxisLine axisLine;

  final MajorTickLines majorTickLines;

  final MinorTickLines minorTickLines;

  final MajorGridLines majorGridLines;

  final MinorGridLines minorGridLines;

  final TextStyle? labelStyle;

  final AxisTitle title;

  final ChartRangePadding rangePadding;

  final int? desiredIntervals;

  final int maximumLabels;

  final int minorTicksPerInterval;

  final int labelRotation;

  final AxisLabelIntersectAction labelIntersectAction;

  final bool opposedPosition;

  final bool isInversed;

  final ChartDataLabelPosition labelPosition;

  final LabelAlignment labelAlignment;

  final TickPosition tickPosition;

  final EdgeLabelPlacement edgeLabelPlacement;

  final double? interval;

  final double plotOffset;

  final String? name;

  final double zoomFactor;

  final double zoomPosition;

  final bool enableAutoIntervalOnZooming;

  final InteractiveTooltip interactiveTooltip;

  final dynamic crossesAt;

  final String? associatedAxisName;

  final bool placeLabelsNearAxisLine;

  final List<PlotBand> plotBands;

  final RangeController? rangeController;

  final double? maximumLabelWidth;

  final double? labelsExtent;

  final int? autoScrollingDelta;

  final AutoScrollingMode autoScrollingMode;

  final Color? borderColor;

  final double borderWidth;

  final AxisBorderType axisBorderType;

  final MultiLevelLabelStyle multiLevelLabelStyle;

  final List<ChartMultiLevelLabel>? multiLevelLabels;

  final MultiLevelLabelFormatterCallback? multiLevelLabelFormatter;

  final ChartLabelFormatterCallback? axisLabelFormatter;
}

class AxisLabel {
  AxisLabel(this.labelStyle, this.labelSize, this.text, this.value, this.trimmedText, this.renderText);

  TextStyle labelStyle;

  Size labelSize;

  String text;

  String? trimmedText;

  String? renderText;

  num value;

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) {
      return true;
    }
    if (other.runtimeType != runtimeType) {
      return false;
    }

    return other is AxisLabel &&
        other.labelStyle == labelStyle &&
        other.labelSize == labelSize &&
        other.text == text &&
        other.trimmedText == trimmedText &&
        other.renderText == renderText &&
        other.value == value;
  }

  @override
  int get hashCode {
    final List<Object?> values = <Object?>[labelStyle, labelSize, text, trimmedText, renderText, value];
    return Object.hashAll(values);
  }

  List<String>? _labelCollection;

  int _index = 1;

  Rect? _labelRegion;

  bool _needRender = true;
}

class MajorTickLines {
  const MajorTickLines({this.size = 5, this.width = 1, this.color});

  final double size;

  final double width;

  final Color? color;

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) {
      return true;
    }
    if (other.runtimeType != runtimeType) {
      return false;
    }

    return other is MajorTickLines && other.size == size && other.width == width && other.color == color;
  }

  @override
  int get hashCode {
    final List<Object?> values = <Object?>[size, width, color];
    return Object.hashAll(values);
  }
}

@immutable
class MinorTickLines {
  const MinorTickLines({this.size = 3, this.width = 0.7, this.color});

  final double size;

  final double width;

  final Color? color;

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) {
      return true;
    }
    if (other.runtimeType != runtimeType) {
      return false;
    }

    return other is MinorTickLines && other.size == size && other.width == width && other.color == color;
  }

  @override
  int get hashCode {
    final List<Object?> values = <Object?>[size, width, color];
    return Object.hashAll(values);
  }
}

@immutable
class MajorGridLines {
  const MajorGridLines({this.width = 0.7, this.color, this.dashArray});

  final List<double>? dashArray;

  final double width;

  final Color? color;

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) {
      return true;
    }
    if (other.runtimeType != runtimeType) {
      return false;
    }

    return other is MajorGridLines && other.dashArray == dashArray && other.width == width && other.color == color;
  }

  @override
  int get hashCode {
    final List<Object?> values = <Object?>[dashArray, width, color];
    return Object.hashAll(values);
  }
}

class MinorGridLines {
  const MinorGridLines({this.width = 0.5, this.color, this.dashArray});

  final List<double>? dashArray;

  final double width;

  final Color? color;

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) {
      return true;
    }
    if (other.runtimeType != runtimeType) {
      return false;
    }

    return other is MinorGridLines && other.dashArray == dashArray && other.width == width && other.color == color;
  }

  @override
  int get hashCode {
    final List<Object?> values = <Object?>[dashArray, width, color];
    return Object.hashAll(values);
  }
}

class AxisTitle {
  AxisTitle({this.text, this.textStyle, this.alignment = ChartAlignment.center});

  final String? text;

  final TextStyle? textStyle;

  final ChartAlignment alignment;

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) {
      return true;
    }
    if (other.runtimeType != runtimeType) {
      return false;
    }

    return other is AxisTitle && other.text == text && other.textStyle == textStyle && other.alignment == alignment;
  }

  @override
  int get hashCode {
    final List<Object?> values = <Object?>[text, textStyle, alignment];
    return Object.hashAll(values);
  }
}

class AxisLine {
  const AxisLine({this.color, this.dashArray, this.width = 1});

  final double width;

  final Color? color;

  final List<double>? dashArray;

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) {
      return true;
    }
    if (other.runtimeType != runtimeType) {
      return false;
    }

    return other is AxisLine && other.dashArray == dashArray && other.width == width && other.color == color;
  }

  @override
  int get hashCode {
    final List<Object?> values = <Object?>[dashArray, width, color];
    return Object.hashAll(values);
  }
}

class VisibleRange {
  VisibleRange(dynamic min, dynamic max) {
    if ((min < max) == true) {
      minimum = min;
      maximum = max;
    } else {
      minimum = max;
      maximum = min;
    }
  }

  dynamic minimum;

  dynamic maximum;

  dynamic interval;

  late num delta;
}

abstract class ChartAxisRenderer with CustomizeAxisElements {
  ChartAxisRenderer();

  late ChartAxisRendererDetails _axisRendererDetails;

  @override
  Color? getAxisLineColor(ChartAxis axis) => axis.axisLine.color;

  @override
  double getAxisLineWidth(ChartAxis axis) => axis.axisLine.width;

  @override
  Color? getAxisMajorTickColor(ChartAxis axis, int majorTickIndex) => axis.majorTickLines.color;

  @override
  double getAxisMajorTickWidth(ChartAxis axis, int majorTickIndex) => axis.majorTickLines.width;

  @override
  Color? getAxisMinorTickColor(ChartAxis axis, int majorTickIndex, int minorTickIndex) => axis.minorTickLines.color;

  @override
  double getAxisMinorTickWidth(ChartAxis axis, int majorTickIndex, int minorTickIndex) => axis.minorTickLines.width;

  @override
  Color? getAxisMajorGridColor(ChartAxis axis, int majorGridIndex) => axis.majorGridLines.color;

  @override
  double getAxisMajorGridWidth(ChartAxis axis, int majorGridIndex) => axis.majorGridLines.width;

  @override
  Color? getAxisMinorGridColor(ChartAxis axis, int majorGridIndex, int minorGridIndex) => axis.minorGridLines.color;

  @override
  double getAxisMinorGridWidth(ChartAxis axis, int majorGridIndex, int minorGridIndex) => axis.minorGridLines.width;

  @override
  String getAxisLabel(ChartAxis axis, String text, int labelIndex) => text;

  @override
  TextStyle getAxisLabelStyle(ChartAxis axis, String text, int labelIndex) => axis.labelStyle!;

  @override
  int getAxisLabelAngle(ChartAxisRenderer axisRenderer, String text, int labelIndex) =>
      axisRenderer._axisRendererDetails.getAxisLabelAngle(axisRenderer, text, labelIndex);

  @override
  void drawHorizontalAxesLine(Canvas canvas, ChartAxisRenderer axisRenderer, CgCartesianChart chart) {
    axisRenderer._axisRendererDetails.drawHorizontalAxesLine(canvas, axisRenderer, chart);
  }

  @override
  void drawVerticalAxesLine(Canvas canvas, ChartAxisRenderer axisRenderer, CgCartesianChart chart) {
    axisRenderer._axisRendererDetails.drawVerticalAxesLine(canvas, axisRenderer, chart);
  }

  @override
  void drawHorizontalAxesTickLines(Canvas canvas, ChartAxisRenderer axisRenderer, CgCartesianChart chart,
      [String? renderType, double? animationFactor, ChartAxisRenderer? oldAxisRenderer, bool? needAnimate,]) {
    axisRenderer._axisRendererDetails.drawHorizontalAxesTickLines(
        canvas, axisRenderer, chart, renderType, animationFactor, oldAxisRenderer, needAnimate,);
  }

  @override
  void drawHorizontalAxesMajorGridLines(Canvas canvas, Offset point, ChartAxisRenderer axisRenderer,
      MajorGridLines grids, int index, CgCartesianChart chart,) {
    axisRenderer._axisRendererDetails
        .drawHorizontalAxesMajorGridLines(canvas, point, axisRenderer, grids, index, chart);
  }

  @override
  void drawHorizontalAxesMinorLines(Canvas canvas, ChartAxisRenderer axisRenderer, double tempInterval, Rect rect,
      num nextValue, int index, CgCartesianChart chart,
      [String? renderType,]) {
    axisRenderer._axisRendererDetails
        .drawHorizontalAxesMinorLines(canvas, axisRenderer, tempInterval, rect, nextValue, index, chart, renderType);
  }

  @override
  void drawVerticalAxesTickLines(Canvas canvas, ChartAxisRenderer axisRenderer, CgCartesianChart chart,
      [String? renderType, double? animationFactor, ChartAxisRenderer? oldAxisRenderer, bool? needAnimate,]) {
    axisRenderer._axisRendererDetails._drawVerticalAxesTickLines(
        canvas, axisRenderer, chart, renderType, animationFactor, oldAxisRenderer, needAnimate,);
  }

  @override
  void drawVerticalAxesMajorGridLines(Canvas canvas, Offset point, ChartAxisRenderer axisRenderer, MajorGridLines grids,
      int index, CgCartesianChart chart,) {
    axisRenderer._axisRendererDetails._drawVerticalAxesMajorGridLines(canvas, point, axisRenderer, grids, index, chart);
  }

  @override
  void drawVerticalAxesMinorTickLines(
      Canvas canvas, ChartAxisRenderer axisRenderer, num tempInterval, Rect rect, int index, CgCartesianChart chart,
      [String? renderType,]) {
    axisRenderer._axisRendererDetails
        ._drawVerticalAxesMinorTickLines(canvas, axisRenderer, tempInterval, rect, index, chart, renderType);
  }

  @override
  void drawHorizontalAxesLabels(Canvas canvas, ChartAxisRenderer axisRenderer, CgCartesianChart chart,
      [String? renderType, double? animationFactor, ChartAxisRenderer? oldAxisRenderer, bool? needAnimate,]) {
    axisRenderer._axisRendererDetails._drawHorizontalAxesLabels(
        canvas, axisRenderer, chart, renderType, animationFactor, oldAxisRenderer, needAnimate,);
  }

  @override
  void drawVerticalAxesLabels(Canvas canvas, ChartAxisRenderer axisRenderer, CgCartesianChart chart,
      [String? renderType, double? animationFactor, ChartAxisRenderer? oldAxisRenderer, bool? needAnimate,]) {
    axisRenderer._axisRendererDetails._drawVerticalAxesLabels(
        canvas, axisRenderer, chart, renderType, animationFactor, oldAxisRenderer, needAnimate,);
  }

  @override
  void drawHorizontalAxesTitle(Canvas canvas, ChartAxisRenderer axisRenderer, CgCartesianChart chart) {
    axisRenderer._axisRendererDetails._drawHorizontalAxesTitle(canvas, axisRenderer, chart);
  }

  @override
  void drawVerticalAxesTitle(Canvas canvas, ChartAxisRenderer axisRenderer, CgCartesianChart chart) {
    axisRenderer._axisRendererDetails._drawVerticalAxesTitle(canvas, axisRenderer, chart);
  }

  num? calculateInterval(VisibleRange range, Size availableSize);

  void applyRangePadding(VisibleRange range, num interval);

  void calculateVisibleRange(Size availableSize);

  void generateVisibleLabels();

  void calculateRange(ChartAxisRenderer axisRenderer) {
    axisRenderer._axisRendererDetails._calculateRange(axisRenderer);
  }

  void dispose() {
    _axisRendererDetails.dispose();
  }
}

class ChartAxisRendererDetails {
  ChartAxisRendererDetails(this.axis, this.stateProperties, this.axisRenderer) {
    visibleLabels = <AxisLabel>[];
    visibleAxisMultiLevelLabels = <AxisMultiLevelLabel>[];
    maximumLabelSize = Size.zero;
    multiLevelLabelTotalSize = Size.zero;
    seriesRenderers = <CartesianSeriesRenderer>[];
    name = axis.name;
    labelRotation = axis.labelRotation;
    zoomFactor = axis.zoomFactor;
    zoomPosition = axis.zoomPosition;
    chartThemeData = stateProperties.renderingDetails.chartTheme;
  }

  late ChartAxisRenderer axisRenderer;

  late ChartAxis axis;

  ChartAxis? oldAxis;

  late CgChartThemeData chartThemeData;

  final CartesianStateProperties stateProperties;

  RenderingDetails get renderingDetails => stateProperties.renderingDetails;

  late CgCartesianChart chart;

  bool isStack100 = false;

  dynamic rangeMinimum;
  dynamic rangeMaximum;

  late List<AxisLabel> visibleLabels;

  late List<AxisMultiLevelLabel> visibleAxisMultiLevelLabels;

  List<Size> multiLevelsMaximumSize = <Size>[];

  Size maximumLabelSize = Size.zero;

  Size multiLevelLabelTotalSize = Size.zero;

  double? axisBorderEnd;

  int? highestLevel;

  AxisOrientation? orientation;

  VisibleRange? visibleRange;

  VisibleRange? actualRange;

  num? minimumMultiLevelLabelValue;
  num? maximumMultiLevelLabelValue;

  bool isMultiLevelLabelEnabled = false;

  late List<CartesianSeriesRenderer> seriesRenderers;

  Rect bounds = Rect.zero;

  bool? isInsideTickPosition;

  late double totalSize;

  double? previousZoomFactor;
  double? previousZoomPosition;

  String? name;

  late int labelRotation;

  late double zoomFactor;
  late double zoomPosition;

  bool isCollide = false;

  num? min;
  num? max;
  num? lowMin;
  num? lowMax;
  num? highMin;
  num? highMax;

  late Size axisSize;

  late ChartAxisRenderer crossAxisRenderer;

  num? crossValue;

  VisibleRange? crossRange;

  double? labelOffset;

  double? titleOffset;

  double? titleHeight;

  Offset? xAxisStart;
  Offset? xAxisEnd;

  num? visibleMinimum;
  num? visibleMaximum;

  int? scrollingDelta;

  int getAxisLabelAngle(ChartAxisRenderer axisRenderer, String text, int labelIndex) {
    final int labelAngle = (axis.labelIntersectAction == AxisLabelIntersectAction.rotate45 && isCollide)
        ? -45
        : (axis.labelIntersectAction == AxisLabelIntersectAction.rotate90 && isCollide)
            ? -90
            : labelRotation;

    return labelAngle;
  }

  void drawHorizontalAxesLine(Canvas canvas, ChartAxisRenderer axisRenderer, CgCartesianChart chart) {
    final Rect rect =
        Rect.fromLTWH(bounds.left - axis.plotOffset, bounds.top, bounds.width + 2 * axis.plotOffset, bounds.height);

    final CustomPaintStyle paintStyle = CustomPaintStyle(axisRenderer.getAxisLineWidth(axis),
        axisRenderer.getAxisLineColor(axis) ?? renderingDetails.chartTheme.axisLineColor, PaintingStyle.stroke,);
    drawDashedPath(canvas, paintStyle, Offset(rect.left, rect.top), Offset(rect.left + rect.width, rect.top),
        axis.axisLine.dashArray,);
    xAxisStart = Offset(rect.left, rect.top);
    xAxisEnd = Offset(rect.left + rect.width, rect.top);
  }

  void drawVerticalAxesLine(Canvas canvas, ChartAxisRenderer axisRenderer, CgCartesianChart chart) {
    final Rect rect =
        Rect.fromLTWH(bounds.left, bounds.top - axis.plotOffset, bounds.width, bounds.height + 2 * axis.plotOffset);
    final CustomPaintStyle paintStyle = CustomPaintStyle(axisRenderer.getAxisLineWidth(axis),
        axisRenderer.getAxisLineColor(axis) ?? renderingDetails.chartTheme.axisLineColor, PaintingStyle.stroke,);
    drawDashedPath(canvas, paintStyle, Offset(rect.left, rect.top), Offset(rect.left, rect.top + rect.height),
        axis.axisLine.dashArray,);
  }

  void drawHorizontalAxesTickLines(Canvas canvas, ChartAxisRenderer axisRenderer, CgCartesianChart chart,
      [String? renderType, double? animationFactor, ChartAxisRenderer? oldAxisRenderer, bool? needAnimate,]) {
    final dynamic chartAxis = axis;
    double betweenTickPositionValue = 0;
    late double tempInterval;
    late double pointX;
    late double pointY;
    int length = visibleLabels.length;
    if (length > 0) {
      final MajorTickLines ticks = axis.majorTickLines;
      final bool isBetweenTicks = (axis is CategoryAxis || axis is DateTimeCategoryAxis) &&
          chartAxis.labelPlacement == LabelPlacement.betweenTicks;
      final num tickBetweenLabel = isBetweenTicks ? 0.5 : 0;
      length += isBetweenTicks ? 1 : 0;
      for (int i = 0; i < length; i++) {
        tempInterval = (isBetweenTicks
                ? i < length - 1
                    ? visibleLabels[i].value - tickBetweenLabel
                    : (visibleLabels[i - 1].value + visibleRange!.interval) - tickBetweenLabel
                : visibleLabels[i].value)
            .toDouble();
        pointX = ((valueToCoefficient(tempInterval, this) * bounds.width) + bounds.left).roundToDouble();
        pointY = bounds.top;
        if (needAnimate!) {
          final double? oldLocation = _getPrevLocation(axisRenderer, oldAxisRenderer!, tempInterval);
          pointX = oldLocation != null ? (oldLocation - (oldLocation - pointX) * animationFactor!) : pointX;
        }
        if (bounds.left.roundToDouble() <= pointX && bounds.right.roundToDouble() >= pointX) {
          if ((axis.majorGridLines.width > 0) == true &&
              renderType == 'outside' &&
              ((axis.plotOffset > 0) == true ||
                  (i != 0 && (isBetweenTicks ? i != length - 1 : i != length)) ||
                  (bounds.left <= pointX && bounds.right >= pointX && !stateProperties.requireInvertedAxis))) {
            axisRenderer.drawHorizontalAxesMajorGridLines(
                canvas, Offset(pointX, pointY), axisRenderer, axis.majorGridLines, i, chart,);
          }
          if ((axis.minorGridLines.width > 0) == true || (axis.minorTickLines.width > 0) == true) {
            num? nextValue = isBetweenTicks
                ? (tempInterval + visibleRange!.interval)
                : i == length - 1
                    ? visibleRange!.maximum
                    : visibleLabels[i + 1].value;
            if (nextValue != null) {
              nextValue = ((valueToCoefficient(nextValue, this) * bounds.width) + bounds.left).roundToDouble();
              axisRenderer.drawHorizontalAxesMinorLines(
                  canvas, axisRenderer, pointX, bounds, nextValue, i, chart, renderType,);
            }
          }
        }
        if (axis.majorTickLines.width > 0 == true &&
            (bounds.left <= pointX && bounds.right.roundToDouble() >= pointX) &&
            renderType == axis.tickPosition.toString().split('.')[1]) {
          drawDashedPath(
              canvas,
              CustomPaintStyle(
                  axisRenderer.getAxisMajorTickWidth(axis, i),
                  axisRenderer.getAxisMajorTickColor(axis, i) ?? renderingDetails.chartTheme.majorTickLineColor,
                  PaintingStyle.stroke,),
              Offset(pointX, pointY),
              Offset(
                  pointX,
                  axis.opposedPosition == false
                      ? (isInsideTickPosition! ? pointY - ticks.size : pointY + ticks.size)
                      : (isInsideTickPosition! ? pointY + ticks.size : pointY - ticks.size),),);
        }
        if ((axis.borderWidth > 0 || isMultiLevelLabelEnabled) && i == 0) {
          setAxisBorderEndPoint(this, pointY);
        }
        if (axis.borderWidth > 0) {
          if (i == 0 && !isBetweenTicks && visibleLabels.length > 1) {
            final num nextPoint = (valueToCoefficient(visibleLabels[i + 1].value, this) * bounds.width) + bounds.left;
            betweenTickPositionValue = (nextPoint.toDouble() - pointX) / 2;
          }
          renderHorizontalAxisBorder(this, canvas, pointX, pointY, isBetweenTicks, betweenTickPositionValue, i);
        }
      }
    }
  }

  void updateRangeControllerValues(ChartAxisRendererDetails axisRenderer) {
    stateProperties.zoomProgress = false;
    stateProperties.isRedrawByZoomPan = false;
    if (axisRenderer is DateTimeAxisRenderer || axisRenderer is DateTimeCategoryAxisRenderer) {
      axisRenderer.rangeMinimum = axis.rangeController!.start.millisecondsSinceEpoch;
      axisRenderer.rangeMaximum = axis.rangeController!.end.millisecondsSinceEpoch;
    } else {
      axisRenderer.rangeMinimum = axis.rangeController!.start;
      axisRenderer.rangeMaximum = axis.rangeController!.end;
    }
  }

  void updateAutoScrollingDelta(int scrollingDelta, ChartAxisRenderer axisRenderer) {
    this.scrollingDelta = scrollingDelta;
    switch (axis.autoScrollingMode) {
      case AutoScrollingMode.start:
        final VisibleRange autoScrollRange =
            VisibleRange(visibleRange!.minimum, visibleRange!.minimum + scrollingDelta);
        autoScrollRange.delta = autoScrollRange.maximum - autoScrollRange.minimum;
        zoomFactor = autoScrollRange.delta / actualRange!.delta;
        zoomPosition = 0;
        break;
      case AutoScrollingMode.end:
        final VisibleRange autoScrollRange =
            VisibleRange(visibleRange!.maximum - scrollingDelta, visibleRange!.maximum);
        autoScrollRange.delta = autoScrollRange.maximum - autoScrollRange.minimum;
        zoomFactor = autoScrollRange.delta / actualRange!.delta;
        zoomPosition = 1 - zoomFactor;
        break;
    }
  }

  void drawHorizontalAxesMajorGridLines(Canvas canvas, Offset point, ChartAxisRenderer axisRenderer,
      MajorGridLines grids, int index, CgCartesianChart chart,) {
    final CustomPaintStyle paintStyle = CustomPaintStyle(
        axisRenderer.getAxisMajorGridWidth(axis, index),
        axisRenderer.getAxisMajorGridColor(axis, index) ?? renderingDetails.chartTheme.majorGridLineColor,
        PaintingStyle.stroke,);
    drawDashedPath(
        canvas,
        paintStyle,
        Offset(point.dx, stateProperties.chartAxis.axisClipRect.top),
        Offset(point.dx, stateProperties.chartAxis.axisClipRect.top + stateProperties.chartAxis.axisClipRect.height),
        grids.dashArray,);
  }

  void drawHorizontalAxesMinorLines(Canvas canvas, ChartAxisRenderer axisRenderer, double tempInterval, Rect rect,
      num nextValue, int index, CgCartesianChart chart,
      [String? renderType,]) {
    double position = tempInterval;
    final MinorTickLines ticks = axis.minorTickLines;
    final num interval = (tempInterval - nextValue).abs() / (axis.minorTicksPerInterval + 1);
    for (int i = 0; i < axis.minorTicksPerInterval; i++) {
      position = axis.isInversed ? (position - interval) : (position + interval);
      final double pointY = rect.top;
      if (axis.minorGridLines.width > 0 &&
          renderType == 'outside' &&
          (bounds.left <= position && bounds.right >= position)) {
        drawDashedPath(
            canvas,
            CustomPaintStyle(
                axisRenderer.getAxisMinorGridWidth(axis, index, i),
                axisRenderer.getAxisMinorGridColor(axis, index, i) ?? renderingDetails.chartTheme.minorGridLineColor,
                PaintingStyle.stroke,),
            Offset(position, stateProperties.chartAxis.axisClipRect.top),
            Offset(
                position, stateProperties.chartAxis.axisClipRect.top + stateProperties.chartAxis.axisClipRect.height,),
            axis.minorGridLines.dashArray,);
      }

      if (axis.minorTickLines.width > 0 &&
          bounds.left <= position &&
          bounds.right >= position &&
          renderType == axis.tickPosition.toString().split('.')[1]) {
        drawDashedPath(
            canvas,
            CustomPaintStyle(
                axisRenderer.getAxisMinorTickWidth(axis, index, i),
                axisRenderer.getAxisMinorTickColor(axis, index, i) ?? renderingDetails.chartTheme.minorTickLineColor,
                PaintingStyle.stroke,),
            Offset(position, pointY),
            Offset(
                position,
                !axis.opposedPosition
                    ? (isInsideTickPosition! ? pointY - ticks.size : pointY + ticks.size)
                    : (isInsideTickPosition! ? pointY + ticks.size : pointY - ticks.size),),
            axis.minorGridLines.dashArray,);
      }
    }
  }

  double _findMultiRows(
      int length, num currentX, AxisLabel currentLabel, ChartAxisRenderer axisRenderer, CgCartesianChart chart,) {
    AxisLabel label;
    num pointX;
    final List<int> labelIndex = <int>[];
    bool isMultiRows;
    for (int i = length - 1; i >= 0; i--) {
      label = visibleLabels[i];
      pointX = (valueToCoefficient(label.value, this) * stateProperties.chartAxis.axisClipRect.width) +
          stateProperties.chartAxis.axisClipRect.left;
      isMultiRows = !axis.isInversed
          ? currentX < (pointX + label.labelSize.width / 2)
          : currentX + currentLabel.labelSize.width > (pointX - label.labelSize.width / 2);
      if (isMultiRows) {
        labelIndex.add(label._index);
        currentLabel._index = (currentLabel._index > label._index) ? currentLabel._index : label._index + 1;
      } else {
        currentLabel._index = labelIndex.contains(label._index) ? currentLabel._index : label._index;
      }
    }
    return currentLabel.labelSize.height * currentLabel._index;
  }

  List<String> _gettingLabelCollection(String currentLabel, num labelsExtent, ChartAxisRenderer axisRenderer,
      AxisLabelIntersectAction labelIntersectAction,) {
    late List<String> textCollection = <String>[];
    if (labelIntersectAction == AxisLabelIntersectAction.wrap) {
      textCollection = currentLabel.split(RegExp(' '));
    } else {
      textCollection.add(currentLabel);
    }
    final List<String> labelCollection = <String>[];
    String text;
    final TextStyle labelStyle = axisRenderer._axisRendererDetails.chartThemeData.axisLabelTextStyle!;
    for (int i = 0; i < textCollection.length; i++) {
      text = textCollection[i];
      (measureText(text, labelStyle, labelRotation).width < labelsExtent)
          ? labelCollection.add(text)
          : labelCollection.add(getTrimmedText(text, labelsExtent, labelStyle,
              axisRenderer: axisRenderer,
              isRtl: axisRenderer._axisRendererDetails.stateProperties.renderingDetails.isRtl,),);
    }
    return labelCollection;
  }

  void calculateZoomRange(ChartAxisRenderer axisRenderer, Size axisSize) {
    ChartAxisRenderer? oldAxisRenderer;
    assert(axis.zoomFactor >= 0 && axis.zoomFactor <= 1, 'The zoom factor of the axis should be between 0 and 1.');
    assert(
        axis.zoomPosition >= 0 && axis.zoomPosition <= 1, 'The zoom position of the axis should be between 0 and 1.',);

    zoomFactor = zoomFactor > 1
        ? 1
        : zoomFactor < 0
            ? 0
            : zoomFactor;
    zoomPosition = zoomPosition > 1
        ? 1
        : zoomPosition < 0
            ? 0
            : zoomPosition;
    if (stateProperties.oldAxisRenderers.isNotEmpty) {
      oldAxisRenderer = getOldAxisRenderer(axisRenderer, stateProperties.oldAxisRenderers);
    }
    if (oldAxisRenderer != null) {
      zoomFactor =
          oldAxisRenderer._axisRendererDetails.axis.zoomFactor != axis.zoomFactor ? axis.zoomFactor : zoomFactor;
      zoomPosition = oldAxisRenderer._axisRendererDetails.axis.zoomPosition != axis.zoomPosition
          ? axis.zoomPosition
          : zoomPosition;
      if (axis.autoScrollingDelta == oldAxisRenderer._axisRendererDetails.axis.autoScrollingDelta) {
        scrollingDelta = oldAxisRenderer._axisRendererDetails.scrollingDelta;
      }
    }

    final VisibleRange baseRange = visibleRange!;
    num start;
    num end;
    start = visibleRange!.minimum + zoomPosition * visibleRange!.delta;
    end = start + zoomFactor * visibleRange!.delta;

    if (start < baseRange.minimum) {
      end = end + (baseRange.minimum - start);
      start = baseRange.minimum;
    }
    if (end > baseRange.maximum) {
      start = start - (end - baseRange.maximum);
      end = baseRange.maximum;
    }
    visibleRange!.minimum = start;
    visibleRange!.maximum = end;
    visibleRange!.delta = end - start;
  }

  void setZoomFactorAndPosition(ChartAxisRenderer axisRenderer, List<ChartAxisRenderer> axisRendererStates) {
    bool didUpdateAxis;
    if (oldAxis != null && (oldAxis!.zoomPosition != axis.zoomPosition || oldAxis!.zoomFactor != axis.zoomFactor)) {
      zoomFactor = axis.zoomFactor;
      zoomPosition = axis.zoomPosition;
      didUpdateAxis = true;
    } else {
      didUpdateAxis = false;
    }
    for (final ChartAxisRenderer zoomedAxisRenderer in stateProperties.zoomedAxisRendererStates) {
      if (zoomedAxisRenderer._axisRendererDetails.name == name) {
        if (didUpdateAxis) {
          zoomedAxisRenderer._axisRendererDetails.zoomFactor = zoomFactor;
          zoomedAxisRenderer._axisRendererDetails.zoomPosition = zoomPosition;
        } else {
          if (axis.autoScrollingDelta == null ||
              scrollingDelta != zoomedAxisRenderer._axisRendererDetails.visibleRange!.delta) {
            zoomFactor = zoomedAxisRenderer._axisRendererDetails.zoomFactor;
            zoomPosition = zoomedAxisRenderer._axisRendererDetails.zoomPosition;
          }
        }
        break;
      }
    }
  }

  void setRangeControllerValues(ChartAxisRenderer axisRenderer) {
    if (axisRenderer is DateTimeAxisRenderer || axisRenderer is DateTimeCategoryAxisRenderer) {
      axis.rangeController!.start =
          DateTime.fromMillisecondsSinceEpoch(axisRenderer._axisRendererDetails.visibleRange!.minimum.toInt());
      axis.rangeController!.end =
          DateTime.fromMillisecondsSinceEpoch(axisRenderer._axisRendererDetails.visibleRange!.maximum.toInt());
    } else {
      axis.rangeController!.start = axisRenderer._axisRendererDetails.visibleRange!.minimum;
      axis.rangeController!.end = axisRenderer._axisRendererDetails.visibleRange!.maximum;
    }
  }

  void setZoomValuesFromRangeController() {
    final ChartAxisRendererDetails axisDetails = AxisHelper.getAxisRendererDetails(axisRenderer);
    if (!(stateProperties.isRedrawByZoomPan || stateProperties.canSetRangeController)) {
      if (stateProperties.rangeChangeBySlider &&
          !stateProperties.canSetRangeController &&
          rangeMinimum != null &&
          rangeMaximum != null) {
        visibleRange!.delta = visibleRange!.maximum - visibleRange!.minimum;
        if (this is! DateTimeCategoryAxisRenderer) {
          visibleRange!.interval = axisRenderer is LogarithmicAxisRenderer
              ? (axisDetails.axisRenderer as LogarithmicAxisRenderer).calculateLogNiceInterval(visibleRange!.delta)
              : axisRenderer.calculateInterval(visibleRange!, axisSize);
        }
        visibleRange!.interval = actualRange!.interval != null && actualRange!.interval % 1 != 0
            ? actualRange!.interval
            : visibleRange!.interval;
        zoomFactor = visibleRange!.delta / (actualRange!.delta);
        zoomPosition = (visibleRange!.minimum - actualRange!.minimum) / actualRange!.delta;
      }
    }
  }

  void setOldRangeFromRangeController() {
    if (!stateProperties.renderingDetails.initialRender! &&
        axis.rangeController != null &&
        !stateProperties.canSetRangeController) {
      final ChartAxisRenderer? oldrenderer = getOldAxisRenderer(axisRenderer, stateProperties.oldAxisRenderers);
      if (oldrenderer != null) {
        final ChartAxisRendererDetails axisRendererDetails = oldrenderer._axisRendererDetails;
        visibleMinimum = rangeMinimum = axisRendererDetails.rangeMinimum is DateTime
            ? axisRendererDetails.rangeMinimum.millisecondsSinceEpoch
            : axisRendererDetails.rangeMinimum;
        visibleMaximum = rangeMaximum = axisRendererDetails.rangeMaximum is DateTime
            ? axisRendererDetails.rangeMaximum.millisecondsSinceEpoch
            : axisRendererDetails.rangeMaximum;
      }
    }
  }

  double? _getPrevLocation(ChartAxisRenderer axisRenderer, ChartAxisRenderer oldAxisRenderer, num value,
      [Size? textSize, num? angle,]) {
    double? location;
    final Rect bounds = axisRenderer._axisRendererDetails.bounds;
    final ChartAxis axis = axisRenderer._axisRendererDetails.axis;
    textSize ??= Size.zero;
    if (oldAxisRenderer._axisRendererDetails.visibleRange!.minimum > value == false) {
      location = axisRenderer._axisRendererDetails.orientation == AxisOrientation.vertical
          ? (axis.isInversed
              ? ((bounds.top + bounds.height) -
                  ((bounds.top -
                          (bounds.top -
                                  (valueToCoefficient(value, oldAxisRenderer._axisRendererDetails) * bounds.height))
                              .roundToDouble())
                      .abs()))
              : (bounds.bottom - (valueToCoefficient(value, oldAxisRenderer._axisRendererDetails) * bounds.height))
                  .roundToDouble())
          : (axis.isInversed
              ? ((valueToCoefficient(value, axisRenderer._axisRendererDetails) * bounds.width) + bounds.right)
                  .roundToDouble()
              : ((valueToCoefficient(value, oldAxisRenderer._axisRendererDetails) * bounds.width) - bounds.left)
                  .roundToDouble());
    } else if (oldAxisRenderer._axisRendererDetails.visibleRange!.maximum < value == false) {
      location = axisRenderer._axisRendererDetails.orientation == AxisOrientation.vertical
          ? (axis.isInversed
              ? (bounds.bottom - (valueToCoefficient(value, oldAxisRenderer._axisRendererDetails) * bounds.height))
                  .roundToDouble()
              : ((bounds.top + bounds.height) -
                  ((bounds.top -
                          (bounds.top -
                                  (valueToCoefficient(value, oldAxisRenderer._axisRendererDetails) * bounds.height))
                              .roundToDouble())
                      .abs())))
          : (axis.isInversed
              ? ((valueToCoefficient(value, oldAxisRenderer._axisRendererDetails) * bounds.width) - bounds.left)
                  .roundToDouble()
              : ((valueToCoefficient(value, axisRenderer._axisRendererDetails) * bounds.width) + bounds.right)
                  .roundToDouble());
    } else {
      if (axisRenderer._axisRendererDetails.orientation == AxisOrientation.vertical) {
        location = (valueToCoefficient(value, oldAxisRenderer._axisRendererDetails) *
                oldAxisRenderer._axisRendererDetails.bounds.height) +
            oldAxisRenderer._axisRendererDetails.bounds.top;
        location =
            ((oldAxisRenderer._axisRendererDetails.bounds.top + oldAxisRenderer._axisRendererDetails.bounds.height) -
                    ((oldAxisRenderer._axisRendererDetails.bounds.top - location).abs())) -
                textSize.height / 2;
      } else {
        location = ((valueToCoefficient(value, oldAxisRenderer._axisRendererDetails) *
                    oldAxisRenderer._axisRendererDetails.bounds.width) +
                oldAxisRenderer._axisRendererDetails.bounds.left)
            .roundToDouble();
        if (angle != null) {
          location -= angle == 0 ? textSize.width / 2 : 0;
        }
      }
    }
    return location;
  }

  double _getPointX(ChartAxisRenderer axisRenderer, Size textSize, Rect axisBounds) {
    late double pointX;
    const num innerPadding = 5;
    final ChartAxisRendererDetails axisRendererDetails = axisRenderer._axisRendererDetails;
    final ChartAxis axis = axisRendererDetails.axis;
    if (axis.borderWidth > 0 || axisRendererDetails.isMultiLevelLabelEnabled) {
      pointX = getLabelOffsetX(axisRendererDetails, textSize);
    } else {
      if (axis.labelPosition == ChartDataLabelPosition.inside) {
        pointX = (!axis.opposedPosition)
            ? axisRendererDetails.labelOffset ??
                (axisBounds.left +
                    innerPadding +
                    (axisRendererDetails.isInsideTickPosition! ? axis.majorTickLines.size : 0))
            : axisRendererDetails.labelOffset != null
                ? axisRendererDetails.labelOffset! - textSize.width
                : (axisBounds.left -
                    axisRendererDetails.maximumLabelSize.width -
                    innerPadding -
                    (axisRendererDetails.isInsideTickPosition! ? axis.majorTickLines.size : 0));
      } else {
        pointX = ((!axis.opposedPosition)
                ? axisRendererDetails.labelOffset != null
                    ? axisRenderer._axisRendererDetails.labelOffset! - textSize.width
                    : (axisBounds.left -
                        (axisRendererDetails.isInsideTickPosition! ? 0 : axis.majorTickLines.size) -
                        textSize.width -
                        innerPadding)
                : (axisRendererDetails.labelOffset ??
                    (axisBounds.left +
                        (axisRendererDetails.isInsideTickPosition! ? 0 : axis.majorTickLines.size) +
                        innerPadding)))
            ;
      }
    }
    return pointX;
  }

  double _getPointY(ChartAxisRenderer axisRenderer, AxisLabel label, Rect axisBounds) {
    final ChartAxisRendererDetails axisRendererDetails = axisRenderer._axisRendererDetails;
    final ChartAxis axis = axisRendererDetails.axis;
    double pointY;
    const num innerPadding = 3;
    if (axis.borderWidth > 0 || axisRendererDetails.isMultiLevelLabelEnabled) {
      pointY = getLabelOffsetY(this, label._index);
    } else {
      if (axis.labelPosition == ChartDataLabelPosition.inside) {
        pointY = axis.opposedPosition == false
            ? axisRendererDetails.labelOffset != null
                ? axisRendererDetails.labelOffset! - axisRendererDetails.maximumLabelSize.height
                : axisBounds.top -
                    innerPadding -
                    (label._index > 1
                        ? axisRendererDetails.maximumLabelSize.height / 2
                        : axisRendererDetails.maximumLabelSize.height) -
                    (axisRendererDetails.isInsideTickPosition! ? axis.majorTickLines.size : 0)
            : axisRendererDetails.labelOffset ??
                axisBounds.top +
                    (axisRendererDetails.isInsideTickPosition! ? axis.majorTickLines.size : 0) +
                    (label._index > 1 ? axisRendererDetails.maximumLabelSize.height / 2 : 0);
      } else {
        pointY = (axis.opposedPosition == false
                ? axisRendererDetails.labelOffset ??
                    (axisBounds.top +
                        ((axisRendererDetails.isInsideTickPosition! ? 0 : axis.majorTickLines.size) + innerPadding) +
                        (label._index > 1 ? axisRendererDetails.maximumLabelSize.height / 2 : 0))
                : axisRendererDetails.labelOffset != null
                    ? axisRendererDetails.labelOffset! - axisRendererDetails.maximumLabelSize.height
                    : (axisBounds.top -
                        (((axisRendererDetails.isInsideTickPosition! ? 0 : axis.majorTickLines.size) + innerPadding) -
                            (label._index > 1 ? axisRendererDetails.maximumLabelSize.height / 2 : 0)) -
                        axisRendererDetails.maximumLabelSize.height))
            ;
      }
    }
    return pointY;
  }

  double _getShiftedPosition(
      ChartAxisRenderer axisRenderer, Rect axisBounds, double pointX, double pointY, Size textSize, int i,) {
    final ChartAxis axis = axisRenderer._axisRendererDetails.axis;
    if (axis.edgeLabelPlacement == EdgeLabelPlacement.shift) {
      if (axis.labelAlignment == LabelAlignment.center) {
        if (i == 0 &&
            ((pointX < axisBounds.left && !axis.isInversed) ||
                (pointX + textSize.width > axisBounds.right && axis.isInversed))) {
          pointX = axis.isInversed ? axisBounds.left + axisBounds.width - textSize.width : axisBounds.left;
        }

        if (i == axisRenderer._axisRendererDetails.visibleLabels.length - 1 &&
            ((((pointX + textSize.width) > axisBounds.right) && !axis.isInversed) ||
                (pointX < axisBounds.left && axis.isInversed))) {
          pointX = axis.isInversed ? axisBounds.left : axisBounds.left + axisBounds.width - textSize.width;
        }
      } else if ((axis.labelAlignment == LabelAlignment.end) &&
          (i == axisRenderer._axisRendererDetails.visibleLabels.length - 1 &&
              ((((pointX + textSize.width) > axisBounds.right) && !axis.isInversed) ||
                  (pointX < axisBounds.left && axis.isInversed)))) {
        pointX = axis.isInversed
            ? axisBounds.left
            : axisBounds.left + axisBounds.width - textSize.width - textSize.height / 2;
      } else if ((axis.labelAlignment == LabelAlignment.start) &&
          (i == 0 &&
              ((pointX < axisBounds.left && !axis.isInversed) ||
                  (pointX + textSize.width > axisBounds.right && axis.isInversed)))) {
        pointX = axis.isInversed
            ? axisBounds.left + axisBounds.width - textSize.width
            : axisBounds.left + textSize.height / 2;
      }
    }

    final Rect currentRegion = axis.labelRotation == 0 &&
            !(axis.labelIntersectAction == AxisLabelIntersectAction.rotate45 ||
                axis.labelIntersectAction == AxisLabelIntersectAction.rotate90)
        ? Rect.fromLTWH(pointX, pointY, textSize.width, textSize.height)
        : Rect.fromLTWH(pointX - textSize.width / 2, pointY - textSize.height / 2, textSize.width, textSize.height);
    final bool isIntersect = i > 0 &&
        i < axisRenderer._axisRendererDetails.visibleLabels.length - 1 &&
        axis.labelIntersectAction == AxisLabelIntersectAction.hide &&
        axis.labelRotation % 180 == 0 &&
        axisRenderer._axisRendererDetails.visibleLabels[i - 1]._labelRegion != null &&
        (axisRenderer._axisRendererDetails.axis.isInversed == false
            ? currentRegion.left < axisRenderer._axisRendererDetails.visibleLabels[i - 1]._labelRegion!.right
            : currentRegion.right > axisRenderer._axisRendererDetails.visibleLabels[i - 1]._labelRegion!.left);
    axisRenderer._axisRendererDetails.visibleLabels[i]._labelRegion = !isIntersect ? currentRegion : null;
    return pointX;
  }

  void _drawVerticalAxesTickLines(Canvas canvas, ChartAxisRenderer axisRenderer, CgCartesianChart chart,
      [String? renderType, double? animationFactor, ChartAxisRenderer? oldAxisRenderer, bool? needAnimate,]) {
    final dynamic axis = axisRenderer._axisRendererDetails.axis;
    final Rect axisBounds = axisRenderer._axisRendererDetails.bounds;
    double betweenTicksPointValue = 0;
    final List<AxisLabel> visibleLabels = axisRenderer._axisRendererDetails.visibleLabels;
    double tempInterval;
    double pointX;
    double pointY;
    int length = visibleLabels.length;
    final bool isBetweenTicks =
        (axis is CategoryAxis || axis is DateTimeCategoryAxis) && axis.labelPlacement == LabelPlacement.betweenTicks;
    final num tickBetweenLabel = isBetweenTicks ? 0.5 : 0;
    length += isBetweenTicks ? 1 : 0;
    for (int i = 0; i < length; i++) {
      tempInterval = (isBetweenTicks
              ? i < length - 1
                  ? visibleLabels[i].value - tickBetweenLabel
                  : (visibleLabels[i - 1].value + axisRenderer._axisRendererDetails.visibleRange!.interval) -
                      tickBetweenLabel
              : visibleLabels[i].value)
          .toDouble();
      pointY =
          (valueToCoefficient(tempInterval, axisRenderer._axisRendererDetails) * axisBounds.height) + axisBounds.top;
      pointY = (axisBounds.top + axisBounds.height) - (pointY - axisBounds.top).abs();
      pointX = axisBounds.left;

      if (needAnimate!) {
        final double? oldLocation = _getPrevLocation(axisRenderer, oldAxisRenderer!, tempInterval);
        pointY = oldLocation != null ? (oldLocation - (oldLocation - pointY) * animationFactor!) : pointY;
      }
      if (pointY >= axisBounds.top && pointY <= axisBounds.bottom) {
        if (axis.majorGridLines.width > 0 == true &&
            renderType == 'outside' &&
            (axis.plotOffset > 0 == true ||
                ((i == 0 || i == length - 1) && chart.plotAreaBorderWidth == 0) ||
                (((i == 0 && axis.opposedPosition == false) || (i == length - 1 && axis.opposedPosition == true)) &&
                    axis.axisLine.width == 0) ||
                (axisBounds.top < pointY - axis.majorGridLines.width &&
                    axisBounds.bottom > pointY + axis.majorGridLines.width))) {
          axisRenderer.drawVerticalAxesMajorGridLines(
              canvas, Offset(pointX, pointY), axisRenderer, axis.majorGridLines, i, chart,);
        }
        if (axis.minorGridLines.width > 0 == true || axis.minorTickLines.width > 0 == true) {
          axisRenderer.drawVerticalAxesMinorTickLines(
              canvas, axisRenderer, tempInterval, axisBounds, i, chart, renderType,);
        }
        if (axis.majorTickLines.width > 0 == true && renderType == axis.tickPosition.toString().split('.')[1]) {
          drawDashedPath(
              canvas,
              CustomPaintStyle(
                  axisRenderer.getAxisMajorTickWidth(axis, i),
                  axisRenderer.getAxisMajorTickColor(axis, i) ?? renderingDetails.chartTheme.majorTickLineColor,
                  PaintingStyle.stroke,),
              Offset(pointX, pointY),
              Offset(
                  axis.opposedPosition == false
                      ? (axisRenderer._axisRendererDetails.isInsideTickPosition!
                          ? pointX + axis.majorTickLines.size
                          : pointX - axis.majorTickLines.size)
                      : (axisRenderer._axisRendererDetails.isInsideTickPosition!
                          ? pointX - axis.majorTickLines.size
                          : pointX + axis.majorTickLines.size),
                  pointY,),);
        }
      }
      if ((axis.borderWidth > 0 == true || isMultiLevelLabelEnabled) && i == 0) {
        setAxisBorderEndPoint(this, pointX);
      }
      if (axis.borderWidth > 0 == true) {
        if (i == 0 && !isBetweenTicks && visibleLabels.length > 1) {
          num nextPoint = (valueToCoefficient(visibleLabels[i + 1].value, this) * bounds.height) + bounds.top;
          nextPoint = (axisBounds.top + axisBounds.height) - (nextPoint - axisBounds.top).abs();
          betweenTicksPointValue = (nextPoint.toDouble() - pointY) / 2;
        }
        renderVerticalAxisBorder(this, canvas, pointX, pointY, isBetweenTicks, betweenTicksPointValue, i);
      }
    }
  }

  void _drawVerticalAxesMajorGridLines(Canvas canvas, Offset point, ChartAxisRenderer axisRenderer,
      MajorGridLines grids, int index, CgCartesianChart chart,) {
    final CustomPaintStyle paintStyle = CustomPaintStyle(
        axisRenderer.getAxisMajorGridWidth(axisRenderer._axisRendererDetails.axis, index),
        axisRenderer.getAxisMajorGridColor(axisRenderer._axisRendererDetails.axis, index) ??
            renderingDetails.chartTheme.majorGridLineColor,
        PaintingStyle.stroke,);
    if (stateProperties.chartAxis.primaryXAxisRenderer!._axisRendererDetails.xAxisStart !=
            Offset(stateProperties.chartAxis.axisClipRect.left, point.dy) &&
        stateProperties.chartAxis.primaryXAxisRenderer!._axisRendererDetails.xAxisEnd !=
            Offset(
                stateProperties.chartAxis.axisClipRect.left + stateProperties.chartAxis.axisClipRect.width, point.dy,)) {
      drawDashedPath(
          canvas,
          paintStyle,
          Offset(stateProperties.chartAxis.axisClipRect.left, point.dy),
          Offset(stateProperties.chartAxis.axisClipRect.left + stateProperties.chartAxis.axisClipRect.width, point.dy),
          grids.dashArray,);
    }
  }

  void _drawVerticalAxesMinorTickLines(
      Canvas canvas, ChartAxisRenderer axisRenderer, num tempInterval, Rect rect, int index, CgCartesianChart chart,
      [String? renderType,]) {
    num value = tempInterval;
    double position = 0;
    final VisibleRange range = visibleRange!;
    final bool rendering =
        axis.minorTicksPerInterval > 0 && (axis.minorGridLines.width > 0 || axis.minorTickLines.width > 0);
    if (rendering) {
      for (int i = 0; i < axis.minorTicksPerInterval; i++) {
        value += range.interval / (axis.minorTicksPerInterval + 1);
        if ((value < range.maximum) && (value > range.minimum)) {
          position = valueToCoefficient(value, axisRenderer._axisRendererDetails) * rect.height;
          position = (position + rect.top).floor().toDouble();
          if (axis.minorGridLines.width > 0 &&
              renderType == 'outside' &&
              rect.top <= position &&
              rect.bottom >= position) {
            drawDashedPath(
                canvas,
                CustomPaintStyle(
                    axisRenderer.getAxisMinorGridWidth(axis, index, i),
                    axisRenderer.getAxisMinorGridColor(axis, index, i) ??
                        renderingDetails.chartTheme.minorGridLineColor,
                    PaintingStyle.stroke,),
                Offset(stateProperties.chartAxis.axisClipRect.left, position),
                Offset(stateProperties.chartAxis.axisClipRect.left + stateProperties.chartAxis.axisClipRect.width,
                    position,),
                axis.minorGridLines.dashArray,);
          }
          if (axis.minorTickLines.width > 0 && renderType == axis.tickPosition.toString().split('.')[1]) {
            drawDashedPath(
                canvas,
                CustomPaintStyle(
                    axisRenderer.getAxisMinorTickWidth(axis, index, i),
                    axisRenderer.getAxisMinorTickColor(axis, index, i) ??
                        renderingDetails.chartTheme.minorTickLineColor,
                    PaintingStyle.stroke,),
                Offset(rect.left, position),
                Offset(
                    !axis.opposedPosition
                        ? (isInsideTickPosition!
                            ? rect.left + axis.minorTickLines.size
                            : rect.left - axis.minorTickLines.size)
                        : (isInsideTickPosition!
                            ? rect.left - axis.minorTickLines.size
                            : rect.left + axis.minorTickLines.size),
                    position,),);
          }
        }
      }
    }
  }

  void _drawHorizontalAxesLabels(Canvas canvas, ChartAxisRenderer axisRenderer, CgCartesianChart chart,
      [String? renderType, double? animationFactor, ChartAxisRenderer? oldAxisRenderer, bool? needAnimate,]) {
    if (renderType == axis.labelPosition.toString().split('.')[1]) {
      final Rect axisBounds = bounds;
      int angle;
      TextStyle textStyle;
      final bool isRtl = axisRenderer._axisRendererDetails.stateProperties.renderingDetails.isRtl;
      late double tempInterval;
      late double pointX;
      late double pointY;
      late double previousLabelEnd;
      for (int i = 0; i < visibleLabels.length; i++) {
        final AxisLabel label = visibleLabels[i];
        final String labelText = axisRenderer.getAxisLabel(axis, label.renderText!, i);
        textStyle = label.labelStyle;
        textStyle = getTextStyle(
            textStyle: textStyle, fontColor: textStyle.color ?? renderingDetails.chartTheme.axisLabelColor,);
        tempInterval = label.value.toDouble();
        angle = axisRenderer.getAxisLabelAngle(axisRenderer, labelText, i);

        if (angle.isNegative) {
          angle = angle + 360;
        }
        labelRotation = angle;
        final Size textSize = measureText(labelText, textStyle);
        final Size rotatedTextSize = measureText(labelText, textStyle, angle);
        pointX =
            ((valueToCoefficient(tempInterval, axisRenderer._axisRendererDetails) * axisBounds.width) + axisBounds.left)
                .roundToDouble();
        pointY = _getPointY(axisRenderer, label, axisBounds);
        pointY -= angle == 0 ? textSize.height / 2 : 0;
        pointY += rotatedTextSize.height / 2;
        pointX -= angle == 0 ? textSize.width / 2 : 0;

        pointX = _getShiftedPosition(axisRenderer, axisBounds, pointX, pointY, rotatedTextSize, i);
        if (axis.labelAlignment == LabelAlignment.end) {
          pointX = pointX + textSize.height / 2;
        } else if (axis.labelAlignment == LabelAlignment.start) {
          pointX = pointX - textSize.height / 2;
        }
        if (axis.edgeLabelPlacement == EdgeLabelPlacement.hide) {
          if (axis.labelAlignment == LabelAlignment.center) {
            if (i == 0 || (i == visibleLabels.length - 1)) {
              visibleLabels[i]._needRender = false;
              continue;
            }
          } else if ((axis.labelAlignment == LabelAlignment.end) &&
              (i == visibleLabels.length - 1 || (i == 0 && axis.isInversed))) {
            visibleLabels[i]._needRender = false;
            continue;
          } else if ((axis.labelAlignment == LabelAlignment.start) &&
              (i == 0 || (i == visibleLabels.length - 1 && axis.isInversed))) {
            visibleLabels[i]._needRender = false;
            continue;
          }
        }

        if (axis.labelIntersectAction == AxisLabelIntersectAction.hide &&
            axis.labelRotation % 180 == 0 &&
            i != 0 &&
            visibleLabels[i - 1]._needRender &&
            (!axis.isInversed ? pointX <= previousLabelEnd : (pointX + textSize.width) >= previousLabelEnd)) {
          continue;
        }

        previousLabelEnd = axis.isInversed ? pointX : pointX + textSize.width;

        if (needAnimate!) {
          final double? oldLocation = _getPrevLocation(axisRenderer, oldAxisRenderer!, tempInterval, textSize, angle);
          pointX = oldLocation != null ? (oldLocation - (oldLocation - pointX) * animationFactor!) : pointX;
        }
        final Offset point = Offset(pointX, pointY);
        if (axisBounds.left - textSize.width <= pointX && axisBounds.right + textSize.width >= pointX) {
          drawText(canvas, labelText, point, textStyle, angle, isRtl);
        }
        if (label._labelCollection != null &&
            label._labelCollection!.isNotEmpty &&
            axis.labelIntersectAction == AxisLabelIntersectAction.wrap) {
          double? finalTextStartPoint;
          for (int j = 1; j < label._labelCollection!.length; j++) {
            final String wrapTxt = label._labelCollection![j];
            finalTextStartPoint = pointY + (j * measureText(wrapTxt, textStyle, angle).height);
            drawText(canvas, wrapTxt, Offset(pointX, finalTextStartPoint), textStyle, angle, isRtl);
            if (j == label._labelCollection!.length - 1) {
              label._labelRegion = Rect.fromLTWH(
                  pointX, pointY, label._labelRegion!.width, finalTextStartPoint + (label.labelSize.height) - pointY,);
            }
          }
        }
      }
    }
  }

  void _drawVerticalAxesLabels(Canvas canvas, ChartAxisRenderer axisRenderer, CgCartesianChart chart,
      [String? renderType, double? animationFactor, ChartAxisRenderer? oldAxisRenderer, bool? needAnimate,]) {
    if (axis.labelPosition.toString().split('.')[1] == renderType) {
      final Rect axisBounds = bounds;
      TextStyle textStyle;
      late double tempInterval;
      late double pointX;
      late double pointY;
      late double previousEnd;
      for (int i = 0; i < visibleLabels.length; i++) {
        final String labelText = axisRenderer.getAxisLabel(axis, visibleLabels[i].renderText!, i);
        final int angle = axisRenderer.getAxisLabelAngle(axisRenderer, labelText, i);
        assert(angle - angle == 0, 'The angle value of the vertical axis must be the whole number.');
        textStyle = visibleLabels[i].labelStyle;
        textStyle = getTextStyle(
            textStyle: textStyle, fontColor: textStyle.color ?? renderingDetails.chartTheme.axisLabelColor,);
        tempInterval = visibleLabels[i].value.toDouble();
        final Size textSize = measureText(labelText, textStyle, 0);
        pointY =
            (valueToCoefficient(tempInterval, axisRenderer._axisRendererDetails) * axisBounds.height) + axisBounds.top;
        pointY = ((axisBounds.top + axisBounds.height) - ((axisBounds.top - pointY).abs())) - textSize.height / 2;
        pointX = _getPointX(axisRenderer, textSize, axisBounds);
        final ChartLocation location = getRotatedTextLocation(pointX, pointY, labelText, textStyle, angle, axis);
        if (axis.labelAlignment == LabelAlignment.center) {
          pointX = location.x;
          pointY = location.y;
        } else if (axis.labelAlignment == LabelAlignment.end) {
          pointX = location.x;
          pointY = location.y - textSize.height / 2;
        } else if (axis.labelAlignment == LabelAlignment.start) {
          pointX = location.x;
          pointY = location.y + textSize.height / 2;
        }
        if (axis.labelIntersectAction == AxisLabelIntersectAction.hide &&
            i != 0 &&
            (!axis.isInversed
                ? pointY + (textSize.height / 2) > previousEnd
                : pointY - (textSize.height / 2) < previousEnd)) {
          continue;
        }
        previousEnd = !axis.isInversed ? pointY - textSize.height / 2 : pointY + textSize.height / 2;

        if (axis.edgeLabelPlacement == EdgeLabelPlacement.shift) {
          if (axis.labelAlignment == LabelAlignment.center) {
            if (i == 0 && axisBounds.bottom <= pointY + textSize.height / 2) {
              pointY = axisBounds.top + axisBounds.height - textSize.height;
            } else if (i == visibleLabels.length - 1 && axisBounds.top >= pointY + textSize.height / 2) {
              pointY = axisBounds.top;
            }
          } else if (axis.labelAlignment == LabelAlignment.start) {
            if (i == 0 && axisBounds.bottom <= pointY + textSize.height / 2) {
              pointY = axisBounds.top + axisBounds.height - textSize.height;
            }
          } else if (axis.labelAlignment == LabelAlignment.end) {
            if (i == visibleLabels.length - 1 && axisBounds.top >= pointY + textSize.height / 2) {
              pointY = axisBounds.top + textSize.height / 2;
            }
          }
        } else if (axis.edgeLabelPlacement == EdgeLabelPlacement.hide) {
          if (axis.labelAlignment == LabelAlignment.center) {
            if (i == 0 || i == visibleLabels.length - 1) {
              continue;
            }
          } else if ((axis.labelAlignment == LabelAlignment.end) &&
              (i == visibleLabels.length - 1 || (i == 0 && axis.isInversed))) {
            continue;
          } else if ((axis.labelAlignment == LabelAlignment.start) &&
              (i == 0 || (i == visibleLabels.length - 1 && axis.isInversed))) {
            continue;
          }
        }
        final Size rotatedTextSize = measureText(labelText, textStyle, angle);
        visibleLabels[i]._labelRegion = axis.labelRotation == 0
            ? Rect.fromLTWH(pointX, pointY, textSize.width, textSize.height)
            : Rect.fromLTWH(pointX - rotatedTextSize.width / 2, pointY - rotatedTextSize.height / 2,
                rotatedTextSize.width, rotatedTextSize.height,);

        if (needAnimate!) {
          final double? oldLocation = _getPrevLocation(axisRenderer, oldAxisRenderer!, tempInterval, textSize);
          pointY = oldLocation != null ? (oldLocation - (oldLocation - pointY) * animationFactor!) : pointY;
        }

        final Offset point = Offset(pointX, pointY);
        if (axisBounds.top - textSize.height <= pointY && axisBounds.bottom + textSize.height >= pointY) {
          drawText(canvas, labelText, point, textStyle, labelRotation,
              axisRenderer._axisRendererDetails.stateProperties.renderingDetails.isRtl,);
        }
      }
    }
  }

  void _drawHorizontalAxesTitle(Canvas canvas, ChartAxisRenderer axisRenderer, CgCartesianChart chart) {
    final Rect axisBounds = bounds;
    Offset point;
    final String title = axis.title.text ?? '';
    const int labelRotation = 0;
    const int innerPadding = 8;
    final TextStyle titleStyle = chartThemeData.axisTitleTextStyle!;
    final Size textSize = measureText(title, titleStyle);
    double top = 0;
    final ChartAxisRendererDetails axisRendererDetails = axisRenderer._axisRendererDetails;
    if ((axis.borderWidth > 0 || isMultiLevelLabelEnabled) && (visibleLabels.isNotEmpty)) {
      top = getHorizontalAxisTitleOffset(this, textSize);
    } else {
      if (axis.labelPosition == ChartDataLabelPosition.inside) {
        top = !axis.opposedPosition
            ? axisRendererDetails.titleOffset ??
                axisBounds.top +
                    (isInsideTickPosition! ? 0 : axis.majorTickLines.size) +
                    (!kIsWeb ? innerPadding : innerPadding + textSize.height / 2)
            : axisRendererDetails.titleOffset != null
                ? axisRendererDetails.titleOffset! - textSize.height
                : axisBounds.top -
                    (isInsideTickPosition! ? 0 : axis.majorTickLines.size) -
                    innerPadding -
                    textSize.height;
      } else {
        top = !axis.opposedPosition
            ? axisRendererDetails.titleOffset ??
                axisBounds.top +
                    (isInsideTickPosition! ? 0 : axis.majorTickLines.size) +
                    innerPadding +
                    (!kIsWeb ? maximumLabelSize.height : maximumLabelSize.height + textSize.height / 2)
            : axisRendererDetails.titleOffset != null
                ? axisRendererDetails.titleOffset! - textSize.height
                : axisBounds.top -
                    (isInsideTickPosition! ? 0 : axis.majorTickLines.size) -
                    innerPadding -
                    maximumLabelSize.height -
                    textSize.height;
      }
    }
    axis.title.alignment == ChartAlignment.near
        ? point = Offset(stateProperties.chartAxis.axisClipRect.left, top)
        : axis.title.alignment == ChartAlignment.far
            ? point = Offset(stateProperties.chartAxis.axisClipRect.right - textSize.width, top)
            : point = Offset(axisBounds.left + ((axisBounds.width / 2) - (textSize.width / 2)), top);
    if (seriesRenderers.isNotEmpty || name == 'primaryXAxis') {
      drawText(canvas, title, point, titleStyle, labelRotation);
    }
  }

  void _drawVerticalAxesTitle(Canvas canvas, ChartAxisRenderer axisRenderer, CgCartesianChart chart) {
    final Rect axisBounds = bounds;
    Offset point;
    final String title = axis.title.text ?? '';
    final int labelRotation = axis.opposedPosition ? 90 : 270;
    const int innerPadding = 10;
    final TextStyle style = chartThemeData.axisTitleTextStyle!;
    final Size textSize = measureText(title, style);
    double left = 0;
    final ChartAxisRendererDetails axisRendererDetails = axisRenderer._axisRendererDetails;
    if (axis.borderWidth > 0 || isMultiLevelLabelEnabled) {
      left = getVerticalAxisTitleOffset(this, textSize);
    } else {
      if (axis.labelPosition == ChartDataLabelPosition.inside) {
        left = (!axis.opposedPosition)
            ? axisRendererDetails.titleOffset != null
                ? axisRendererDetails.titleOffset! - textSize.height / 2
                : axisBounds.left -
                    (isInsideTickPosition! ? 0 : axis.majorTickLines.size) -
                    innerPadding -
                    textSize.height
            : axisRendererDetails.titleOffset != null
                ? axisRendererDetails.titleOffset! + textSize.height / 2
                : axisBounds.left + (isInsideTickPosition! ? 0 : axis.majorTickLines.size) + innerPadding * 2;
      } else {
        left = (!axis.opposedPosition)
            ? axisRendererDetails.titleOffset != null
                ? axisRendererDetails.titleOffset! - textSize.height
                : (axisBounds.left -
                    (isInsideTickPosition! ? 0 : axis.majorTickLines.size) -
                    innerPadding -
                    maximumLabelSize.width -
                    textSize.height / 2)
            : axisRendererDetails.titleOffset != null
                ? axisRendererDetails.titleOffset! + textSize.height / 2
                : axisBounds.left +
                    (isInsideTickPosition! ? 0 : axis.majorTickLines.size) +
                    innerPadding +
                    maximumLabelSize.width +
                    textSize.height / 2;
      }
    }
    axis.title.alignment == ChartAlignment.near
        ? point = Offset(left, stateProperties.chartAxis.axisClipRect.bottom - textSize.width / 2)
        : axis.title.alignment == ChartAlignment.far
            ? point = Offset(left, stateProperties.chartAxis.axisClipRect.top + textSize.width / 2)
            : point = Offset(left, axisBounds.top + (axisBounds.height / 2));
    if (seriesRenderers.isNotEmpty || name == 'primaryYAxis') {
      drawText(canvas, title, point, style, labelRotation);
    }
  }

  void _findAdditionalRange(ChartAxisRenderer axisRenderer, num start, num end, num interval) {
    num minimum;
    num maximum;
    minimum = ((start / interval).floor()) * interval;
    maximum = ((end / interval).ceil()) * interval;
    if (axis.rangePadding == ChartRangePadding.additional) {
      minimum -= interval;
      maximum += interval;
    }

    _updateActualRange(axisRenderer, minimum, maximum, interval);
  }

  void _updateActualRange(ChartAxisRenderer axisRenderer, num minimum, num maximum, num interval) {
    final dynamic chartAxis = axis;
    final ChartAxisRendererDetails axisDetails = AxisHelper.getAxisRendererDetails(axisRenderer);
    actualRange!.minimum = chartAxis.minimum == null
        ? minimum
        : (axisDetails is DateTimeCategoryAxisDetails
            ? (axisDetails.getEffectiveRange(chartAxis.minimum, true)! -
                (chartAxis.labelPlacement != LabelPlacement.onTicks ? 0 : 0.5))
            : chartAxis.minimum);
    actualRange!.maximum = chartAxis.maximum == null
        ? maximum
        : (axisDetails is DateTimeCategoryAxisDetails
            ? (axisDetails.getEffectiveRange(chartAxis.maximum, true)! +
                (chartAxis.labelPlacement != LabelPlacement.onTicks ? 0 : 0.5))
            : chartAxis.maximum);
    if (actualRange!.maximum == 0 && actualRange!.minimum == actualRange!.maximum) {
      actualRange!.maximum += 1;
    }
    actualRange!.delta = actualRange!.maximum - actualRange!.minimum;
    actualRange!.interval = axis.interval ?? interval;
  }

  void _findNormalRange(ChartAxisRenderer axisRenderer, num start, num end, num interval) {
    final dynamic chartAxis = axis;
    num remaining;
    num minimum;
    num maximum;
    num startValue = start;
    if ((axis is CategoryAxis || axis is DateTimeCategoryAxis) && chartAxis.labelPlacement == LabelPlacement.onTicks) {
      minimum = start - 0.5;
      maximum = end + 0.5;
    } else {
      if (start < 0) {
        if (start.isNegative && end.isNegative) {
          minimum = start > ((5.0 / 6.0) * end) ? 0 : start - (end - start) / 2;
        } else {
          startValue = 0;
          minimum = start + (start / 20);
        }
        remaining = interval + getValueByPercentage(minimum, interval);
        if ((0.365 * interval) >= remaining) {
          minimum -= interval;
        }
        if (getValueByPercentage(minimum, interval) < 0) {
          minimum = (minimum - interval) - getValueByPercentage(minimum, interval);
        }
      } else {
        minimum = start < ((5.0 / 6.0) * end) ? 0 : (start - (end - start) / 2);
        if (minimum % interval > 0) {
          minimum -= minimum % interval;
        }
      }
      maximum = (end > 0) ? (end + (end - startValue) / 20) : (end - (end - startValue) / 20);
      remaining = interval - (maximum % interval);
      if ((0.365 * interval) >= remaining) {
        maximum += interval;
      }
      if (maximum % interval > 0) {
        maximum = end > 0 ? (maximum + interval) - (maximum % interval) : (maximum + interval) + (maximum % interval);
      }
    }
    if (minimum == 0) {
      interval = (axisRenderer is NumericAxisRenderer)
          ? calculateNumericNiceInterval(axisRenderer, maximum - minimum, axisSize)
          : axisRenderer.calculateInterval(VisibleRange(minimum, maximum), axisSize)!;
      maximum = (maximum / interval).ceil() * interval;
    }

    _updateActualRange(axisRenderer, minimum, maximum, interval);
  }

  void triggerLabelRenderEvent(String labelText, num labelValue,
      [DateTimeIntervalType? currentDateTimeIntervalType, String? currentDateFormat,]) {
    TextStyle fontStyle = chartThemeData.axisLabelTextStyle!.copyWith();
    final String actualText = labelText;
    String renderText = actualText;
    String? eventActualText;
    final AxisLabelRenderDetails axisLabelDetails =
        AxisLabelRenderDetails(labelValue, actualText, fontStyle, axis, currentDateTimeIntervalType, currentDateFormat);
    if (axis.axisLabelFormatter != null) {
      final ChartAxisLabel axisLabel = axis.axisLabelFormatter!(axisLabelDetails);
      fontStyle = fontStyle.merge(axisLabel.textStyle);
      renderText = axisLabel.text;
      eventActualText = axisLabel.text;
    }
    Size textSize = measureText(renderText, fontStyle, 0);
    if (axis.maximumLabelWidth != null || axis.labelsExtent != null) {
      if (axis.maximumLabelWidth != null) {
        assert(axis.maximumLabelWidth! >= 0, 'maximumLabelWidth must not be negative');
      }
      if (axis.labelsExtent != null) {
        assert(axis.labelsExtent! >= 0, 'labelsExtent must not be negative');
      }
      if ((axis.maximumLabelWidth != null && textSize.width > axis.maximumLabelWidth!) ||
          (axis.labelsExtent != null && textSize.width > axis.labelsExtent!)) {
        labelText = getTrimmedText(renderText, (axis.labelsExtent ?? axis.maximumLabelWidth)!, fontStyle,
            axisRenderer: axisRenderer,
            isRtl: axisRenderer._axisRendererDetails.stateProperties.renderingDetails.isRtl,);
      }
      textSize = measureText(labelText, fontStyle, 0);
    }
    final String? trimmedText = labelText.contains('...') || labelText.isEmpty ? labelText : null;
    renderText = trimmedText ?? renderText;
    final Size labelSize = measureText(renderText, fontStyle, axis.labelRotation);
    visibleLabels
        .add(AxisLabel(fontStyle, labelSize, eventActualText ?? actualText, labelValue, trimmedText, renderText));
  }

  void calculateMaximumLabelSize(ChartAxisRenderer axisRenderer, CartesianStateProperties stateProperties) {
    AxisLabelIntersectAction action;
    AxisLabel label;
    double maximumLabelHeight = 0;
    double maximumLabelWidth = 0;
    double labelMaximumWidth;
    double pointX;
    action = axis.labelIntersectAction;
    labelMaximumWidth = stateProperties.chartAxis.axisClipRect.width / visibleLabels.length;
    if (orientation == AxisOrientation.horizontal &&
        axis.labelIntersectAction != AxisLabelIntersectAction.none &&
        visibleLabels.length > 1) {
      final Rect axisBounds = stateProperties.chartAxis.axisClipRect;
      AxisLabel label1;
      num pointX1;
      for (int i = 0; i < visibleLabels.length - 1; i++) {
        label = visibleLabels[i];
        pointX =
            (valueToCoefficient(label.value, axisRenderer._axisRendererDetails) * axisBounds.width) + axisBounds.left;
        pointX -= label.labelSize.width / 2;
        pointX = (i == 0 &&
                axis.edgeLabelPlacement == EdgeLabelPlacement.shift &&
                ((pointX < axisBounds.left && !axis.isInversed) ||
                    (pointX + label.labelSize.width > axisBounds.right && axis.isInversed)))
            ? (axis.isInversed ? axisBounds.left + axisBounds.width - label.labelSize.width : axisBounds.left)
            : ((i == visibleLabels.length - 1 &&
                    axis.edgeLabelPlacement == EdgeLabelPlacement.shift &&
                    ((((pointX + label.labelSize.width) > axisBounds.right) && !axis.isInversed) ||
                        (pointX < axisBounds.left && axis.isInversed)))
                ? (axis.isInversed ? axisBounds.left : axisBounds.left + axisBounds.width - label.labelSize.width)
                : pointX);

        label1 = visibleLabels[i + 1];
        pointX1 = (valueToCoefficient(label1.value, axisRenderer._axisRendererDetails) *
                stateProperties.chartAxis.axisClipRect.width) +
            stateProperties.chartAxis.axisClipRect.left;
        pointX1 -= label1.labelSize.width / 2;
        if ((((pointX + label.labelSize.width) > pointX1) && !axis.isInversed) ||
            (((pointX - label.labelSize.width) < pointX1) && axis.isInversed)) {
          isCollide = true;
          break;
        }
      }
    }

    for (int i = 0; i < visibleLabels.length; i++) {
      label = visibleLabels[i];
      if (label.labelSize.width > maximumLabelWidth) {
        maximumLabelWidth = label.labelSize.width;
      }
      if (label.labelSize.height > maximumLabelHeight) {
        maximumLabelHeight = label.labelSize.height;
      }

      if (orientation == AxisOrientation.horizontal) {
        pointX = (valueToCoefficient(label.value, axisRenderer._axisRendererDetails) *
                stateProperties.chartAxis.axisClipRect.width) +
            stateProperties.chartAxis.axisClipRect.left;
        pointX -= label.labelSize.width / 2;

        if (isCollide) {
          final List<double> list = _performLabelIntersectAction(
              label, action, maximumLabelWidth, maximumLabelHeight, labelMaximumWidth, pointX, i, axisRenderer, chart,);
          maximumLabelWidth = list[0];
          maximumLabelHeight = list[1];
        }
      }
    }
    maximumLabelSize = Size(maximumLabelWidth, maximumLabelHeight);
  }

  List<double> _performLabelIntersectAction(
      AxisLabel label,
      AxisLabelIntersectAction action,
      double maximumLabelWidth,
      double maximumLabelHeight,
      double labelMaximumWidth,
      num pointX,
      int i,
      ChartAxisRenderer axisRenderer,
      CgCartesianChart chart,) {
    double height;
    int angle = labelRotation;
    Size currentLabelSize;
    switch (action) {
      case AxisLabelIntersectAction.multipleRows:
        if (i > 0) {
          height = _findMultiRows(i, pointX, label, axisRenderer, chart);
          if (height > maximumLabelHeight) {
            maximumLabelHeight = height;
          }
        }
        break;
      case AxisLabelIntersectAction.rotate45:
      case AxisLabelIntersectAction.rotate90:
        angle = action == AxisLabelIntersectAction.rotate45 ? -45 : -90;
        labelRotation = angle;
        currentLabelSize = measureText(label.renderText!, chartThemeData.axisLabelTextStyle!, angle);
        if (currentLabelSize.height > maximumLabelHeight) {
          maximumLabelHeight = currentLabelSize.height;
        }
        if (currentLabelSize.width > maximumLabelWidth) {
          maximumLabelWidth = currentLabelSize.width;
        }
        break;
      case AxisLabelIntersectAction.wrap:
      case AxisLabelIntersectAction.trim:
        label._labelCollection = _gettingLabelCollection(label.renderText!, labelMaximumWidth, axisRenderer, action);
        if (label._labelCollection!.isNotEmpty) {
          label.renderText = label._labelCollection![0];
        }
        height = label.labelSize.height * label._labelCollection!.length;
        if (height > maximumLabelHeight) {
          maximumLabelHeight = height;
        }
        break;
      default:
        break;
    }
    return <double>[maximumLabelWidth, maximumLabelHeight];
  }

  void _calculateRange(ChartAxisRenderer axisRenderer) {
    min = null;
    max = null;
    CartesianSeriesRenderer seriesRenderer;
    double paddingInterval = 0;
    ChartAxisRendererDetails xAxisDetails;
    ChartAxisRendererDetails yAxisDetails;
    num? minimumX;
    num? maximumX;
    num? minimumY;
    num? maximumY;
    String seriesType;
    final ChartAxisRendererDetails axisDetails = AxisHelper.getAxisRendererDetails(axisRenderer);
    for (int i = 0; i < seriesRenderers.length; i++) {
      seriesRenderer = seriesRenderers[i];
      final SeriesRendererDetails seriesRendererDetails = SeriesHelper.getSeriesRendererDetails(seriesRenderer);
      minimumX = seriesRendererDetails.minimumX;
      maximumX = seriesRendererDetails.maximumX;
      minimumY = seriesRendererDetails.minimumY;
      maximumY = seriesRendererDetails.maximumY;
      seriesType = seriesRendererDetails.seriesType;
      if (seriesRendererDetails.visible! == true &&
          minimumX != null &&
          maximumX != null &&
          minimumY != null &&
          maximumY != null) {
        paddingInterval = 0;
        xAxisDetails = seriesRendererDetails.xAxisDetails!;
        yAxisDetails = seriesRendererDetails.yAxisDetails!;
        if (((xAxisDetails is DateTimeAxisDetails || xAxisDetails is NumericAxisDetails) &&
                xAxisDetails.axis.rangePadding == ChartRangePadding.auto) &&
            (seriesType.contains('column') ||
                (seriesType.contains('bar') && seriesType.contains('errorbar') == false) ||
                seriesType == 'histogram')) {
          seriesRendererDetails.minDelta = seriesRendererDetails.minDelta ??
              calculateMinPointsDelta(xAxisDetails.axisRenderer, seriesRenderers, stateProperties);
          paddingInterval = seriesRendererDetails.minDelta! / 2;
        }
        if (((stateProperties.requireInvertedAxis ? yAxisDetails : xAxisDetails) == axisDetails) &&
            orientation == AxisOrientation.horizontal) {
          stateProperties.requireInvertedAxis
              ? findMinMax(minimumY, maximumY)
              : findMinMax(minimumX - paddingInterval, maximumX + paddingInterval);
        }

        if (((stateProperties.requireInvertedAxis ? xAxisDetails : yAxisDetails) == axisDetails) &&
            orientation == AxisOrientation.vertical) {
          stateProperties.requireInvertedAxis
              ? findMinMax(minimumX - paddingInterval, maximumX + paddingInterval)
              : findMinMax(minimumY, maximumY);
        }
      }
    }
  }

  void findMinMax(num minVal, num maxVal) {
    if (min == null || min! > minVal) {
      min = minVal;
    }
    if (max == null || max! < maxVal) {
      max = maxVal;
    }
  }

  num calculateNumericNiceInterval(ChartAxisRenderer axisRenderer, num delta, Size size) {
    final List<num> intervalDivisions = <num>[10, 5, 2, 1];
    num niceInterval;

    final num actualDesiredIntervalCount = calculateDesiredIntervalCount(size, axisRenderer);
    niceInterval = delta / actualDesiredIntervalCount;
    if (axisRenderer._axisRendererDetails.axis.desiredIntervals != null) {
      return niceInterval;
    }

    final num minimumInterval = niceInterval == 0 ? 0 : math.pow(10, calculateLogBaseValue(niceInterval, 10).floor());
    for (int i = 0; i < intervalDivisions.length; i++) {
      final num interval = intervalDivisions[i];
      final num currentInterval = minimumInterval * interval;
      if (actualDesiredIntervalCount < (delta / currentInterval)) {
        break;
      }
      niceInterval = currentInterval;
    }
    return niceInterval;
  }

  num calculateDesiredIntervalCount(Size availableSize, ChartAxisRenderer axisRenderer) {
    final num size = axisRenderer._axisRendererDetails.orientation == AxisOrientation.horizontal
        ? availableSize.width
        : availableSize.height;
    if (axisRenderer._axisRendererDetails.axis.desiredIntervals == null) {
      num desiredIntervalCount =
          (axisRenderer._axisRendererDetails.orientation == AxisOrientation.horizontal ? 0.533 : 1) *
              axisRenderer._axisRendererDetails.axis.maximumLabels;
      desiredIntervalCount = math.max(size * (desiredIntervalCount / 100), 1);
      return desiredIntervalCount;
    } else {
      return axisRenderer._axisRendererDetails.axis.desiredIntervals!;
    }
  }

  ChartRangePadding calculateRangePadding(ChartAxisRenderer axisRenderer, CgCartesianChart chart) {
    final ChartAxis axis = axisRenderer._axisRendererDetails.axis;
    ChartRangePadding padding = ChartRangePadding.auto;
    if (axis.rangePadding != ChartRangePadding.auto) {
      padding = axis.rangePadding;
    } else if (axis.rangePadding == ChartRangePadding.auto && axisRenderer._axisRendererDetails.orientation != null) {
      switch (axisRenderer._axisRendererDetails.orientation!) {
        case AxisOrientation.horizontal:
          padding = stateProperties.requireInvertedAxis
              ? (isStack100 ? ChartRangePadding.round : ChartRangePadding.normal)
              : ChartRangePadding.none;
          break;
        case AxisOrientation.vertical:
          padding = !stateProperties.requireInvertedAxis
              ? (isStack100 ? ChartRangePadding.round : ChartRangePadding.normal)
              : ChartRangePadding.none;
          break;
      }
    }
    return padding;
  }

  void applyRangePaddings(
      ChartAxisRenderer axisRenderer, CartesianStateProperties stateProperties, VisibleRange range, num interval,) {
    final num start = range.minimum;
    final num end = range.maximum;
    final ChartRangePadding padding = calculateRangePadding(axisRenderer, chart);
    if (padding == ChartRangePadding.additional || padding == ChartRangePadding.round) {
      _findAdditionalRange(axisRenderer, start, end, interval);
    } else if (padding == ChartRangePadding.normal) {
      _findNormalRange(axisRenderer, start, end, interval);
    } else {
      _updateActualRange(axisRenderer, start, end, interval);
    }
    range.delta = range.maximum - range.minimum;
  }

  void dispose() {
    visibleLabels.clear();
    seriesRenderers.clear();
  }
}

class AxisHelper {
  const AxisHelper._();

  static ChartAxisRendererDetails getAxisRendererDetails(ChartAxisRenderer axisRenderer) {
    return axisRenderer._axisRendererDetails;
  }

  static Rect? getLabelRegion(AxisLabel label) {
    return label._labelRegion;
  }

  static void setAxisRendererDetails(ChartAxisRenderer renderer, ChartAxisRendererDetails rendererDetails) {
    renderer._axisRendererDetails = rendererDetails;
  }
}
